﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Citrix.Cmp;
using Citrix.XenApp.Commands;

namespace Citrix.XaAdminMobile.Model
{
    /// <summary>
    /// The model object for a XenApp server. Used to get state about the server.
    /// </summary>
    internal class ServerModel : IServerModel
    {
        /// <summary>
        /// Used to sync access to internal state.
        /// </summary>
        private object stateLock = new object();

        /// <summary>
        /// The name of the server.
        /// </summary>
        private readonly string serverName;

        /// <summary>
        /// The XenApp load index.
        /// </summary>
        private uint loadIndex;

        /// <summary>
        /// XA PS SDK object that is the source of most of the server state.
        /// </summary>
        private XAServer xaServer;

        /// <summary>
        /// Initialisation ctor to instantiate off a XAPSSDK server object.
        /// </summary>
        /// <param name="source">Source server object.</param>
        /// <exception cref="ArgumentNullException">Thrown if source is null.</exception>
        internal ServerModel(XAServer source)
        {
            if (null == source)
            {
                throw new ArgumentNullException("source");
            }
            this.serverName = source.ServerName;
            this.xaServer = source;
        }

        /// <summary>
        /// Initialisation ctor to instantiate off a XAPSSDK load object.
        /// </summary>
        /// <param name="load">Source load object.</param>
        /// <exception cref="ArgumentNullException">Thrown if load is null.</exception>
        internal ServerModel(XAServerLoad load)
        {
            if (null == load)
            {
                throw new ArgumentNullException("load");
            }
            this.serverName = load.ServerName;
            this.loadIndex = load.Load;
        }

        /// <summary>
        /// The date/time the Citrix server was installed.
        /// </summary>
        public DateTime? CitrixInstallDate
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixInstallDate : null;
                }
            }
        }

        /// <summary>
        /// The install path of the Citrix server.
        /// </summary>
        public string CitrixInstallPath
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixInstallPath : null;
                }
            }
        }

        /// <summary>
        /// The Citrix product edition.
        /// </summary>
        public ProductEdition? CitrixProductEdition
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixEdition : null;
                }
            }
        }

        /// <summary>
        /// The product name of the Citrix (Metaframe/PS/XenApp) server.
        /// </summary>
        public string CitrixProductName
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixProductName : null;
                }
            }
        }

        /// <summary>
        /// The service pack (if any) applied to the Citrix server.
        /// </summary>
        public string CitrixServicePack
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixServicePack : null;
                }
            }
        }


        /// <summary>
        /// The Citrix product version.
        /// </summary>
        public Version CitrixVersion
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.CitrixVersion : null;
                }
            }
        }

        /// <summary>
        /// Whether or not the operating system is 64 bit.
        /// </summary>
        public bool? Is64Bit
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.Is64Bit : null;
                }
            }
        }

        /// <summary>
        /// The ICA (HDX) port number this server is listening on for incoming connections.
        /// </summary>
        public Int32? IcaPortNumber
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.IcaPortNumber : null;
                }
            }
        }

        /// <summary>
        /// The IP addresses of the server. It includes IPV4 and IPV6 addresses.
        /// </summary>
        public string[] IPAddresses
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.IPAddresses : null;
                }
            }
        }

        /// <summary>
        /// Whether or not the print spooler is healthy on this server.
        /// </summary>
        public bool? IsSpoolerHealthy
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.IsSpoolerHealthy : null;
                }
            }
        }

        /// <summary>
        /// The name of the Citrix license server that this server is using.
        /// </summary>
        public string LicenseServerName
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.LicenseServerName : null;
                }
            }
        }

        /// <summary>
        /// The port of the Citrix license server that this server is using.
        /// </summary>
        public Int32? LicenseServerPort
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.LicenseServerPortNumber : null;
                }
            }
        }

        /// <summary>
        /// The XenApp load index. Values are [0..10000] for normal load values, and 20000 in
        /// special cases to indicate the server is unavailable.
        /// </summary>
        public uint Load
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.loadIndex;
                }
            }
        }

        /// <summary>
        /// Whether or not logons are enabled. Null when the value is not known.
        /// </summary>
        public bool? LogonsEnabled
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.LogOnsEnabled : null;
                }
            }
        }

        /// <summary>
        /// The logon mode of the server.
        /// </summary>
        public LogOnMode LogonMode
        {
            get
            {
                lock (this.stateLock)
                {
                    LogOnMode? mode = null;
                    if (null != this.xaServer)
                    {
                        mode = this.xaServer.LogOnMode;
                    }
                    if (!mode.HasValue)
                    {
                        mode = LogOnMode.Unknown;
                    }

                    return mode.Value;
                }
            }
        }

        /// <summary>
        /// The name of the server.
        /// </summary>
        public string Name
        {
            get
            {
                lock (this.stateLock)
                {
                    return this.serverName;
                }
            }
        }

        /// <summary>
        /// The service pack applied to the operating system.
        /// </summary>
        public string OsServicePack
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.OSServicePack : null;
                }
            }
        }


        /// <summary>
        /// The version of the operating system.
        /// </summary>
        public Version OsVersion
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.OSVersion : null;
                }
            }
        }

        /// <summary>
        /// The power and capacity management mode.
        /// </summary>
        public PcmMode PowerCapManagementMode
        {
            get
            {
                lock (this.stateLock)
                {
                    PcmMode? mode = null;
                    if (null != this.xaServer)
                    {
                        mode = this.xaServer.PcmMode;
                    }

                    if (!mode.HasValue)
                    {
                        mode = PcmMode.Unknown;
                    }
                    return mode.Value;
                }
            }
        }

        /// <summary>
        /// The RDP port number this server is listening on for incoming RDP connections.
        /// </summary>
        public Int32? RdpPortNumber
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.RdpPortNumber : null;
                }
            }
        }

        /// <summary>
        /// The number of sessions currently running on the server, null when unknown.
        /// </summary>
        public int? SessionCount
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.SessionCount : null;
                }
            }
        }

        /// <summary>
        /// The zone election preference of this server.
        /// </summary>
        public ZoneElectionPreference ZoneElectionPreference
        {
            get
            {
                lock (this.stateLock)
                {
                    ZoneElectionPreference? preference = null;
                    if (null != this.xaServer)
                    {
                        preference = this.xaServer.ElectionPreference;
                    }

                    if (!preference.HasValue)
                    {
                        preference = ZoneElectionPreference.Unknown;
                    }
                    return preference.Value;
                }
            }
        }

        /// <summary>
        /// The name of the zone this server belongs to.
        /// </summary>
        public string ZoneName
        {
            get
            {
                lock (this.stateLock)
                {
                    return (null != this.xaServer) ? this.xaServer.ZoneName : null;
                }
            }
        }

        /// <summary>
        /// Updates our internal state from the XA SDK server object. We assume the server
        /// object has the full set of properties loaded.
        /// </summary>
        /// <param name="source">Source server object.</param>
        internal void Update(XAServer source)
        {
            HashSet<string> changed = new HashSet<string>();

            lock (this.stateLock)
            {
                if (null == this.xaServer || this.xaServer.CitrixInstallDate != source.CitrixInstallDate)
                {
                    changed.Add("CitrixInstallDate");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.CitrixInstallPath, source.CitrixInstallPath, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("CitrixInstallPath");
                }
                if (null == this.xaServer || this.xaServer.CitrixEdition != source.CitrixEdition)
                {
                    changed.Add("CitrixProductEdition");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.CitrixProductName, source.CitrixProductName, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("CitrixProductName");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.CitrixServicePack, source.CitrixServicePack, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("CitrixServicePack");
                }
                if (null == this.xaServer || this.xaServer.CitrixVersion != source.CitrixVersion)
                {
                    changed.Add("CitrixVersion");
                }
                if (null == this.xaServer || this.xaServer.Is64Bit != source.Is64Bit)
                {
                    changed.Add("Is64Bit");
                }
                if (null == this.xaServer || this.xaServer.IcaPortNumber != source.IcaPortNumber)
                {
                    changed.Add("IcaPortNumber");
                }
                if (null == this.xaServer || !Equals(this.xaServer.IPAddresses, source.IPAddresses))
                {
                    changed.Add("IPAddresses");
                }
                if (null == this.xaServer || this.xaServer.IsSpoolerHealthy != source.IsSpoolerHealthy)
                {
                    changed.Add("IsSpoolerHealthy");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.LicenseServerName, source.LicenseServerName, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("LicenseServerName");
                }
                if (null == this.xaServer || this.xaServer.LicenseServerPortNumber != source.LicenseServerPortNumber)
                {
                    changed.Add("LicenseServerPort");
                }
                if (null == this.xaServer || this.xaServer.LogOnsEnabled != source.LogOnsEnabled)
                {
                    changed.Add("LogonsEnabled");
                }
                if (null == this.xaServer || this.xaServer.LogOnMode != source.LogOnMode)
                {
                    changed.Add("LogonMode");
                }
                if (null == this.xaServer || !string.Equals(this.serverName, source.ServerName, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("Name");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.OSServicePack, source.OSServicePack, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("OsServicePack");
                }
                if (null == this.xaServer || this.xaServer.OSVersion != source.OSVersion)
                {
                    changed.Add("OsVersion");
                }
                if (null == this.xaServer || this.xaServer.PcmMode != source.PcmMode)
                {
                    changed.Add("PowerCapManagementMode");
                }
                if (null == this.xaServer || this.xaServer.RdpPortNumber != source.RdpPortNumber)
                {
                    changed.Add("RdpPortNumber");
                }
                if (null == this.xaServer || this.xaServer.SessionCount != source.SessionCount)
                {
                    changed.Add("SessionCount");
                }
                if (null == this.xaServer || this.xaServer.ElectionPreference != source.ElectionPreference)
                {
                    changed.Add("ZoneElectionPreference");
                }
                if (null == this.xaServer || !string.Equals(this.xaServer.ZoneName, source.ZoneName, StringComparison.OrdinalIgnoreCase))
                {
                    changed.Add("ZoneName");
                }
                this.xaServer = source;
            }

            foreach (var s in changed)
            {
                OnPropertyChanged(s);
            }
        }

        /// <summary>
        /// Used to compare two string arrays for equality.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        private static bool Equals(string[] lhs, string[] rhs)
        {
            if (null == lhs && null == rhs)
            {
                return true;
            }
            else if (null != lhs && null != rhs)
            {
                if (lhs.Length == rhs.Length)
                {
                    for (int i = 0; i < lhs.Length; ++i)
                    {
                        if (!string.Equals(lhs[i], rhs[i], StringComparison.OrdinalIgnoreCase))
                        {
                            return false;
                        }
                    }
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Updates our internal load value.
        /// </summary>
        /// <param name="load">Source load object.</param>
        internal void UpdateLoad(XAServerLoad load)
        {
            bool changed = false;
            lock (this.stateLock)
            {
                if (this.loadIndex != load.Load)
                {
                    changed = true;
                    this.loadIndex = load.Load;
                }
            }

            if (changed)
            {
                OnPropertyChanged("Load");
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fired whenever a public property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property change event. Maybe called from any thread - we always fire the
        /// event on the main UI thread.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        private void OnPropertyChanged(string propertyName)
        {
            NotifyHelper.VerifyProperty(propertyName, this);
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion // INotifyPropertyChanged Members
    }
}
